/*
When reading large quantities of data, the readLine() function shown in Listing 59-1
is inefficient, since a system call is required to read each character. A more efficient
interface would read a block of characters into a buffer and extract a line at a time from
this buffer. Such an interface might consist of two functions. The first of these
functions, which might be called readLineBufInit(fd, &rlbuf), initializes the bookkeeping
data structure pointed to by rlbuf. This structure includes space for a data buffer,
the size of that buffer, and a pointer to the next “unread” character in that buffer.
It also includes a copy of the file descriptor given in the argument fd. The second
function, readLineBuf(&rlbuf), returns the next line from the buffer associated with
rlbuf. If required, this function reads a further block of data from the file descriptor
saved in rlbuf. Implement these two functions. Modify the programs in Listing 59-6
( is_seqnum_sv.c ) and Listing 59-7 ( is_seqnum_cl.c ) to use these functions.
*/

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <ctype.h>
#include <netdb.h>
#include <unistd.h>

#include "../common/logger.h"

#define PORT_NUM "5000"
#define BACKLOG 50

struct line_buffer
{
  int fd;
  char *data;
  size_t length;
  size_t unread;
};

static void
read_line_buffer_init(int fd, struct line_buffer *lbuf)
{
#define LENGTH 100
  lbuf->fd = fd;
  lbuf->data = malloc(LENGTH);
  lbuf->length = 0;
  lbuf->unread = 0;
}

static char *
read_line_buffer(struct line_buffer *lbuf)
{
  if (lbuf->unread == lbuf->length)
    lbuf->unread = 0;
  ssize_t bytes = read(lbuf->fd, lbuf->data, LENGTH);
  if (bytes == -1)
    {
      log_system_error("read failed");
      return NULL;
    }
  warnx("debug: %d bytes read", bytes);
  lbuf->length = bytes;
  for (size_t n = lbuf->unread; n < lbuf->length; ++n)
    {
      fprintf(stderr, "%c", lbuf->data[n]);
      if (lbuf->data[n] == '\n')
        {
          log_system_warning("debug: inside");
          char *line;
          size_t line_size = n - lbuf->unread + 2;
          line = malloc(line_size);
          strncpy(line, lbuf->data, line_size - 2);
          line[line_size - 1] = '\0';
          lbuf->unread = n + 1;
          return line;
        }
    }

  return NULL;
}

void
read_line_buffer_deinit(struct line_buffer *lbuf)
{
  free(lbuf->data);
  lbuf->fd = 0;
  lbuf->data = NULL;
  lbuf->length = 0;
  lbuf->unread = 0;
}

int
main(int argc, char *argv[])
{
  if (argc > 1 && strcmp(argv[1], "--help") == 0)
    {
      printf("%s [init-seq-num]\n", argv[0]);
      exit(EXIT_SUCCESS);
    }

  uint32_t seq_num = argc == 2 ? atoi(argv[1]) : 0;

  struct addrinfo hint;
  struct addrinfo *result, *rp;
  memset(&hint, 0, sizeof hint);
  hint.ai_canonname = NULL;
  hint.ai_addr = NULL;
  hint.ai_next = NULL;
  hint.ai_socktype = SOCK_STREAM;
  hint.ai_family = AF_UNSPEC;
  hint.ai_flags = AI_PASSIVE | AI_NUMERICSERV;

  if (getaddrinfo(NULL, PORT_NUM, &hint, &result) != 0)
    log_system_error("unable to getaddrinfo");

  int fd, optval = 1;
  for (rp = result; rp != NULL; rp = rp->ai_next)
    {
      fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if (fd == -1) continue;

      if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
          == -1) log_system_error("unable to set sockopt");

      if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0)
        break;

      close(fd);
    }

  if (rp == NULL)
    log_error("couldn't bind socket to any address");

  freeaddrinfo(result);

  if (listen(fd, BACKLOG) == -1)
    log_system_error("listen");

  struct sockaddr_storage client_addr;
  int client_fd;
  char addr_str[INET_ADDRSTRLEN];
  char host[NI_MAXHOST];
  char service[NI_MAXSERV];
  socklen_t socklen = sizeof client_addr;
  struct line_buffer line_buffer;
  for (;;)
    {
      client_fd = accept(fd, (struct sockaddr *) &client_addr,
                        &socklen);
      if (client_fd == -1)
        {
          log_system_warning("accept");
          continue;
        }

      if (getnameinfo((struct sockaddr *) &client_addr, socklen,
                      host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
        snprintf(addr_str, INET_ADDRSTRLEN, "%s:%s", host, service);
      else
        snprintf(addr_str, INET_ADDRSTRLEN, "(?UNKNOWN?)");
      printf("Connection from %s\n", addr_str);

      read_line_buffer_init(client_fd, &line_buffer);
      char *line;
      while ((line = read_line_buffer(&line_buffer)) != NULL)
        {
          printf("%s", line);
          free(line);
        }

      if (close(client_fd) == -1)
        log_system_error("close");
      read_line_buffer_deinit(&line_buffer);
    }

  return EXIT_SUCCESS;
}
